package biz.inspeed.test.buffer;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * the method invoke sequence should be </br>
 * 1. {@link #add(byte[], int)}</br>
 * 2. {@link #getPackets()}</br>
 * 3. {@link #clear()}
 * */
public class Buffer  {

	private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
	private final Lock readLock = rwl.readLock();
	private final Lock writeLock = rwl.writeLock();

	private  byte[] buffer = new byte[128];
	private  int index = 0;

	/** the index of last parseIndex for clearing the buffer to hold the effective data*/
	private int clearIndex = 0;
//	static int state = 0;
//	static byte size = 0;
//	static byte checksum = 0;
//	static byte[] packet = null;
//	static byte indexOfPacket = 0;
	List<byte[]> packets = new ArrayList<byte[]>();

	public List<byte[]> getPackets() {
		readLock.lock();
		try {
			 int state = 0;
			 byte size = 0;
			 byte checksum = 0;
			 byte[] packet = null;
			 byte indexOfPacket = 0;

//			List<byte[]> packets = new ArrayList<byte[]>();
			for (int i = 0; i < index; i++) {
				switch (state) { 
				case 0:
					if (this.buffer[i] == 0) {
						state = 1;
					}
					break;
				case 1:
					if (this.buffer[i] == 0) {
						state = 2; 
					} else {
						state = 0;
					}
					break;
				case 2:/// this case i will change latter
					if (this.buffer[i] == (byte) 0xff) {
						state = 3;
					} else if (this.buffer[i] == 0x00) {
						state = 2;
					} else {
						state = 0;
					}
					break;
				case 3:
					size = buffer[i];
					if (size == 0) {
						state = 31;
						// / meet ACK frame
						packet = new byte[6];
						packet[0] = 0x00;packet[1] = 0x00;packet[2] = (byte) 0xFF;packet[3] = (byte) 0x00;
						indexOfPacket = 4;
//						if (this.try2GetAck(i) > 0) {
//							System.arraycopy(this.buffer, i - 3, packet, 0, 6);
//							packets.add(packet);
//						}
					} else {
						state = 4;
					}
					break;
				case 4:
					if (((byte) size + buffer[i]) != (byte) 0x00) {
						// error
						state = 0;
					} else {
						state = 5;
					}
					break;
				case 5:
					if (buffer[i] == (byte) 0xd5 || buffer[i] == (byte)0x7f) {
						state = 6;
						packet = new byte[7 + size];
						
						packet[0] = 0x00;packet[1] = 0x00;packet[2] = (byte) 0xFF;//packet[3] = (byte) 0x00;
						packet[3] = size;packet[4] = (byte)(0-size);packet[5] =buffer[i]; 
						
						indexOfPacket = 6;
//						System.arraycopy(buffer, i - 5, packet, 0, 6);
						checksum = (byte) (checksum + buffer[i]);
					} else {
						state = 0;
					}
					break;
				case 6:
					packet[indexOfPacket] = buffer[i];
					checksum = (byte) (checksum + buffer[i]);
					indexOfPacket++;
					/// the last element is 0 ,omit it to assign in case 7
					if (indexOfPacket >= ((byte) size + 7 -1)) {
						state = 7;
					}
					break;
				case 7:
					packet[indexOfPacket] = buffer[i];
					if (checksum == 0x00) {
						packets.add(packet);
						clearIndex = i +1;
					}
					state = 0;
					break;
				case 31:///ACK
					packet[indexOfPacket] = buffer[i];
					indexOfPacket ++;
					if(indexOfPacket >= packet.length){
						state = 0;
						packets.add(packet);
						clearIndex = i +1;
					}
					break;
				default:
					state = 0;
					break;
				}

			}
			return packets;
		} finally {
			readLock.unlock();
		}
	}

	/**
	 * copy byteArray(between 0 and size) into buffer
	 * 
	 * @param byteArray
	 * @param size
	 */
	public void add(byte[] byteArray, int size) {
		writeLock.lock();
		try {
			System.arraycopy(byteArray, 0, this.buffer, this.index, size);
			this.index += size;
		} finally {
			writeLock.unlock();
		}
	}

	/**
	 * clear buffer<br>
	 * remove the entire packet in the buffer remain the tail to parse next time
	 * use {@link #clearIndex} to make sure the clear is OK.If the buffer is all of dirty data
	 * we should do something 
	 */
	public void clear() {
		writeLock.lock();
		try {
//			this.index = 0;
//			Arrays.fill(this.buffer, 0, buffer.length - 1, (byte) 0x00);
//			packets.clear();
			
			///fixed 2012-07-04 10:10 by @author ACNTWWW@GMAIL.COM
			packets.clear();
			///transfer data
			/// move effective data forward and clear the position
			for(int i = clearIndex; i < index; i ++){
				buffer[i-clearIndex] = buffer[i];
				buffer[i] = (byte)0x00;
			}
			///change index
			index = index -clearIndex;
			/// clear data [index, clearIndex]
			for(int i = index; i <=clearIndex; i ++){
				buffer[i] = (byte)0x00;
			}
			/// reset clearIndex = 0;
		} finally {
			writeLock.unlock();
		}
	}

	@Override
	public Buffer clone() {
		Buffer b = new Buffer();
		b.index = this.index;
		byte[] _buffer = new byte[128];
		System.arraycopy(this.buffer, 0, _buffer, 0, 128);
		return b;
	}

	@Override
	public String toString() {
		if (index == 0) {
			return null;
		}
		StringBuffer sBuffer = new StringBuffer();
		for (int i = 0; i < index; i++) {
			sBuffer.append(String.format("%02X", buffer[i])).append(" ");
		}
		if (index > 0) {
			sBuffer.deleteCharAt(sBuffer.length() - 1);
		}
		return sBuffer.toString();
	}

}
